//
//  CoreData.swift
//  ClassManager
//
//  Created by wqh on 1/27/21.
//

import SwiftUI
var encoder = JSONEncoder(), decoder = JSONDecoder()
let MIN: TimeInterval = 60
let DAY: TimeInterval = MIN*60*24
let WEEK: TimeInterval = DAY*7 //一周经过的secs

class CoreData: ObservableObject {
    @Published var events: [event] = []
    @Published var classes: [_class] = []
    @Published var sortedClasses: [_class] = []
    var count_classes = 0, count_events = 0, classNameStr: String
    var fileProviderError: Error? = nil
    @Published var FILE_PROVIDER_IS_UPLOADING_FLAG = false
    @Published var FILE_DOWNLOAD_ISNOT_FINISHED_FLAG = true
    @Published var IS_INTERNET_OPERATION_ALLOWED_FLAG = true
    init(classNameStr: String) {
        self.classNameStr = classNameStr
//        sleep(1)
        let events = UserDefaults().data(forKey: "event"),
            classes = UserDefaults().data(forKey: "class")
        if let events = events {
            self.events = (try? decoder.decode([event].self, from: events)) ?? []
        }
        if let classes = classes {
            self.classes = (try? decoder.decode([_class].self, from: classes)) ?? []
        }
        for _ in self.events {
            self.events[count_events].id = count_events
            count_events += 1
        }
//        // // print(self.events)
        for _ in self.classes {
            self.classes[count_classes].id = count_classes
//            self.classes[count_classes].offset = 0
            count_classes += 1
        }
        updateClassProgress()
    }
    
    func addClass(name: String) {
        withAnimation(.bouncy) {
            self.classes.append(_class(id: count_classes, name: name))
        }
        count_classes += 1
        _ = self.uploadDataByHttp()
//        save()
    }
    func removeClass_confirmed(at offsets: IndexSet) {
        RunAfterVierified {
            withAnimation(.bouncy) {
                let index = offsets[offsets.startIndex]
                for (id, _event) in self.events.enumerated() {
                    if _event._class == index {
                        self.events[id].isRemoved = true
                    }
                    if _event._class > index {
                        self.events[_event.id]._class -= 1
                    }
                }
                self.classes.remove(atOffsets: offsets)
                self.count_classes = 0
                for _ in self.classes {
                    self.classes[self.count_classes].id = self.count_classes
                    self.count_classes += 1
                }
//                self.save()
                self.updateClassProgress()
                _ = self.uploadDataByHttp()
            }
        }
    }
    private func updateClassProgress() {
        for index in 0..<classes.count {
            classes[index].progress = [CGFloat](repeating: 0, count: 3)
        }
        for _event in self.events {
            if !_event.isRemoved && !_event.isNotForProgress {
                switch _event.state {
                case 0: classes[_event._class].progress[0] += 1;break
                case 1: classes[_event._class].progress[1] += 1;break
                case 2: classes[_event._class].progress[2] += 1;break
                default: break;
                }
            }
        }
        for (index, _) in classes.enumerated() {
            let sum = classes[index].sum()
            if sum != 0 {
                classes[index].progress[0] /= sum
                classes[index].progress[1] /= sum
                classes[index].progress[2] /= sum
            }
        }
        self.sortedClasses = self.classes.sorted(by: sortClass)
//        // // print(self.classes)
    }
    private func sortClass(_ a: _class, b: _class) -> Bool {
        return a.progress[0] > b.progress[0]
    }
    
    func addevent(_class: Int, start: TimeInterval, end: TimeInterval, teacher: String, _repeat: Bool, state: Int) {
        if events.count >= 990 {
            return
        }
        withAnimation(.easeInOut) {
            self.events.append(event(id: count_events
                                       , _class: _class, start: start, end: end, techer: teacher, state: state, _repeat: _repeat))
        }
        if end - Date().timeIntervalSince1970 < 0 {
            self.events[count_events].isNotForProgress = true
        }
        count_events += 1
        _ = uploadDataByHttp()
//        save()
    }
    func editevent(index: Int, _class: Int, start: TimeInterval, end: TimeInterval, teacher: String, _repeat: Bool, state: Int) {
        self.events[index]._class = _class
        self.events[index].start = start
        self.events[index].end = end
        self.events[index].techer = teacher
        self.events[index]._repeat = _repeat
        self.events[index].state = state
        if end - Date().timeIntervalSince1970 < 0 {
            self.events[index].isNotForProgress = true
        }
//        save()
        updateClassProgress()
        _ = uploadDataByHttp()
    }
    func changeeventState(at Index: Int, to State: _state) {
        withAnimation(.default) {
            switch State {
            case .unknown: self.events[Index].state = -1;break
            case .succeded: self.events[Index].state = 0;break
            case .failed: self.events[Index].state = 1;break
            case .warning: self.events[Index].state = 2;break
            }
            updateClassProgress()
        }
        _ = uploadDataByHttp()
//        save()
    }
    func repeateventNextWeek(at index: Int) {
        events[index]._repeat = false
        addevent(_class: events[index]._class,
                           start: events[index].start + WEEK,
                           end: events[index].end + WEEK,
                           teacher: events[index].techer,
                           _repeat: true, state: -1)
//        save()
        _ = uploadDataByHttp()
    }
    func removeevent(at offsets: IndexSet) {
        RunAfterVierified {
            withAnimation(.default) {
                self.events.remove(atOffsets: offsets)
            }
            self.count_events = 0
            for _ in self.events {
                self.events[self.count_events].id = self.count_events
                self.count_events += 1
            }
            self.updateClassProgress()
            _ = self.uploadDataByHttp()
//            self.save()
        }
    }
    
    @available(*, renamed: "downloadDataByHttp()")
    func downloadDataByHttp(stateCode: Binding<Bool>? = nil, completion: (() -> Void)? = nil) -> Bool {
        if !IS_INTERNET_OPERATION_ALLOWED_FLAG || FILE_PROVIDER_IS_UPLOADING_FLAG
        {
            print("not allowed")
            return false
        }
        DispatchQueue.main.async {
            self.FILE_DOWNLOAD_ISNOT_FINISHED_FLAG = true
            self.IS_INTERNET_OPERATION_ALLOWED_FLAG = false
        }
        var serverStr = (UserDefaults(suiteName: "group.classmanager")!.string(forKey: "httpServer") ?? "http://255.255.255.255") +
        (UserDefaults(suiteName: "group.classmanager")!.string(forKey: "downloadFrom") ?? "")
        serverStr = serverStr.replacingOccurrences(of: "$fileName$", with: "ClassManager\(classNameStr).json")
        let server = URL(string: serverStr)
        guard let server = server else {
            DispatchQueue.main.async {
                self.FILE_DOWNLOAD_ISNOT_FINISHED_FLAG = false
            }
            let error = NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey:"Unknown server address."]) as Error
            self.fileProviderError = error
            return false
        }
        let req = URLRequest(url: server, cachePolicy: .reloadIgnoringLocalCacheData)
        let session = URLSession.shared.dataTask(with: req) { (contents, response, error) in
            if let error = error {
                self.fileProviderError = error
                DispatchQueue.main.async {
                    self.FILE_DOWNLOAD_ISNOT_FINISHED_FLAG = false
                    self.IS_INTERNET_OPERATION_ALLOWED_FLAG = true
                }
            }
            else {
//                print("---DOWNLOAD_RESPONSE----")
//                print(response)
//                print("---END---")
                self.downloadedDataHandler(contents, completion: completion)
                DispatchQueue.main.async {
                    self.FILE_DOWNLOAD_ISNOT_FINISHED_FLAG = false
                    self.IS_INTERNET_OPERATION_ALLOWED_FLAG = true
                    if let stateCode = stateCode {
                        stateCode.wrappedValue = true
                    }
                }
            }
        }
        session.resume()
//        print(server, "Download was called at", Date())
        return true
    }
    
    func downloadDataByHttp() async -> CoreData {
        return await withCheckedContinuation { continuation in
            _ = downloadDataByHttp() {
                continuation.resume(returning: self)
            }
        }
    }
    
    private func downloadedDataHandler(_ contents: Data?, completion: (() -> Void)?)
    {
        DispatchQueue.main.async {
            if let contents = contents {
                let data = (try? decoder.decode(ExportedData.self, from: contents)) ?? ExportedData(event: [], classes: [])
//                print(data)
                if data.event.count == 0 && data.classes.count == 0 && !data.isRealNoData {
                    self.FILE_DOWNLOAD_ISNOT_FINISHED_FLAG = false
                    return
                }
                let events = data.event
                self.events = events
                self.count_events = 0
                for _ in self.events {
                    self.events[self.count_events].id = self.count_events
                    self.count_events += 1
                }
                let classes = data.classes
                withAnimation(.spring) {
                    self.classes = classes
                }
                self.count_classes = 0
                for _ in self.classes {
                    self.classes[self.count_classes].id = self.count_classes
        //            self.classes[count_classes].offset = 0
                    self.count_classes += 1
                }
                self.fileProviderError = nil
                self.FILE_DOWNLOAD_ISNOT_FINISHED_FLAG = false
                print(data, "Download was finished at", Date())
                self.updateClassProgress()
            }
            self.IS_INTERNET_OPERATION_ALLOWED_FLAG = true
            if let completion = completion {
                completion()
            }
        }
    }
    
    @available(*, renamed: "uploadDataByHttp()")
    func uploadDataByHttp(completion: (() -> Void)? = nil) -> Bool {
        DispatchQueue.main.async {
            self.FILE_PROVIDER_IS_UPLOADING_FLAG = true
        }
        var serverStr = (UserDefaults(suiteName: "group.classmanager")!.string(forKey: "httpServer") ?? "") + (UserDefaults(suiteName: "group.classmanager")!.string(forKey: "postTo") ?? "")
        serverStr = serverStr.replacingOccurrences(of: "$fileName$", with: "ClassManager\(classNameStr).json")
        let server = URL(string: serverStr)
        guard let server = server else {
            DispatchQueue.main.async {
                self.FILE_PROVIDER_IS_UPLOADING_FLAG = false
            }
            let error = NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey:"Unknown server address."]) as Error
            self.fileProviderError = error
            return false
        }
        let exportedData = exportData()
        var req = URLRequest(url: server, cachePolicy: .reloadIgnoringLocalCacheData)
        req.httpMethod = "POST"
        let task = URLSession.shared.uploadTask(with: req, from: exportedData) { (data, response, error) in
            print("---POSTBACK----")
            if let response = response {
                print(response)
                print(String(data: data!, encoding: .utf8)!)
            }
            if error != nil {
                print(error!)
                self.fileProviderError = error
            }
            DispatchQueue.main.async {
                self.FILE_PROVIDER_IS_UPLOADING_FLAG = false
            }
            print("----END----")
            completion?()
        }
        task.resume()
        return true
    }
    
    func uploadDataByHttp() async -> CoreData {
        return await withCheckedContinuation { continuation in
            _ = uploadDataByHttp(completion: {
                continuation.resume(returning: self)
            })
        }
    }
    
    private func exportData() -> Data {
        var eventForEncode: [event] = []
        for _event in events {
            if !_event.isRemoved {
                eventForEncode.append(_event)
            }
        }
        let exportedData = try! encoder.encode(ExportedData(event: eventForEncode, classes: self.classes,
                                                            isRealNoData: (eventForEncode.count == 0 && self.classes.count == 0)))
        return exportedData
    }
}

class ClassNameData: ObservableObject {
    @Published var ClassName: [className]
    var fileProviderError: Error? = nil
    @Published var FILE_PROVIDER_IS_UPLOADING_FLAG = false
    @Published var FILE_DOWNLOAD_ISNOT_FINISHED_FLAG = true
    @Published var IS_INTERNET_OPERATION_ALLOWED_FLAG = false
    init() {
        self.ClassName = []
    }
    
    @available(*, renamed: "downloadDataByHttp()")
    func downloadDataByHttp(completion: (() -> Void)? = nil) -> Bool {
        if !IS_INTERNET_OPERATION_ALLOWED_FLAG {
            return false
        }
        DispatchQueue.main.async {
            self.FILE_DOWNLOAD_ISNOT_FINISHED_FLAG = true
        }
        var serverStr = (UserDefaults(suiteName: "group.classmanager")!.string(forKey: "httpServer") ?? "http://255.255.255.255") +
        (UserDefaults(suiteName: "group.classmanager")!.string(forKey: "downloadFrom") ?? "")
        serverStr = serverStr.replacingOccurrences(of: "$fileName$", with: "Index_ClassManager.json")
        let server = URL(string: serverStr)
        guard let server = server else {
            FILE_DOWNLOAD_ISNOT_FINISHED_FLAG = false
            let error = NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey:"Unknown server address."]) as Error
            self.fileProviderError = error
            return false
        }
        let req = URLRequest(url: server, cachePolicy: .reloadIgnoringLocalCacheData)
        // // print(server)
        let session = URLSession.shared.dataTask(with: req) { (contents, response, error) in
            if let error = error {
                DispatchQueue.main.async {
                    self.FILE_DOWNLOAD_ISNOT_FINISHED_FLAG = false;
                }
                self.fileProviderError = error
            }
            else {
                self.downloadedDataHandler(contents, completion: completion)
            }
        }
        session.resume()
        print(server, "Download was called at", Date())
        return true
    }
    
    func downloadDataByHttp() async -> ClassNameData {
        return await withCheckedContinuation { continuation in
            _ = downloadDataByHttp() {
                continuation.resume(returning: self)
            }
        }
    }
    
    
    private func downloadedDataHandler(_ contents: Data?, completion: (() -> Void)?)
    {
        if !IS_INTERNET_OPERATION_ALLOWED_FLAG {
            return
        }
        DispatchQueue.main.async {
            if let contents = contents {
                let data = (try? decoder.decode(classNameExported.self, from: contents)) ?? classNameExported(ClassName: [], isRealNoData: false)
                  print(data)
                if self.FILE_PROVIDER_IS_UPLOADING_FLAG {
                    DispatchQueue.main.async {
                        self.FILE_DOWNLOAD_ISNOT_FINISHED_FLAG = false
                    }
                    return
                }
                self.ClassName = data.ClassName
                self.fileProviderError = nil
                DispatchQueue.main.async {
                    self.FILE_DOWNLOAD_ISNOT_FINISHED_FLAG = false
                }
            }
            self.FILE_DOWNLOAD_ISNOT_FINISHED_FLAG = false
            if let completion = completion {
                completion()
            }
        }
    }
    
    func uploadDataByHttp() -> Bool {
        DispatchQueue.main.async {
            self.FILE_PROVIDER_IS_UPLOADING_FLAG = true
        }
        var serverStr = (UserDefaults(suiteName: "group.classmanager")!.string(forKey: "httpServer") ?? "") + (UserDefaults(suiteName: "group.classmanager")!.string(forKey: "postTo") ?? "")
        serverStr = serverStr.replacingOccurrences(of: "$fileName$", with: "Index_ClassManager.json")
        let server = URL(string: serverStr)
        guard let server = server else {
            FILE_PROVIDER_IS_UPLOADING_FLAG = false
            let error = NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey:"Unknown server address."]) as Error
            self.fileProviderError = error
            return false
        }
//        ftpConnector.delegate = self
        var req = URLRequest(url: server, cachePolicy: .reloadIgnoringLocalCacheData)
        req.httpMethod = "POST"
        let exportedData = exportData()
        URLSession.shared.uploadTask(with: req, from: exportedData) { (_, response, error) in
            if let error = error {
                self.fileProviderError = error
            }
            DispatchQueue.main.async {
                self.FILE_PROVIDER_IS_UPLOADING_FLAG = false
            }
        }.resume()
        if fileProviderError != nil {
            FILE_PROVIDER_IS_UPLOADING_FLAG = false
            return false
        }
        return true
    }
    
    private func exportData() -> Data {
        var processedClassName: [className] = []
        for _className in ClassName {
            if !_className.isDeleted {
                processedClassName.append(_className)
            }
        }
        let exportedData = try! encoder.encode(classNameExported(ClassName: processedClassName,
                                                                 isRealNoData: self.ClassName.count == 0))
        return exportedData
    }
}

struct className: Identifiable, Codable {
    var id = UUID()
    var name: String
    var isDeleted: Bool = false
    var BLEName: String? = nil
}

struct classNameExported: Codable {
    var ClassName: [className]
    var isRealNoData: Bool
}

struct event: Identifiable, Codable {
    var id: Int = 0
    var _class: Int
    var start: TimeInterval
    var end: TimeInterval
    var techer: String
    var state = -1
    var _repeat = false
    var isRemoved = false
    var isNotForProgress = false
}
struct _class: Identifiable, Codable {
    var id: Int = 0
    var name = ""
    var progress = [CGFloat](repeating: 0, count: 3)
//    var offset: CGFloat = 0
}

struct ExportedData: Codable {
    var event: [event]
    var classes: [_class]
    var isRealNoData = false
}

enum _state {
    case unknown //-1
    case succeded //0
    case failed //1
    case warning //2
}

extension _class {
    func sum() -> CGFloat {
        return progress[0] + progress[1] + progress[2]
    }
}

extension Int {
    func convertToString() -> String {
        switch self {
        case -1:
            return "未签到"
        case 0:
            return "已签到"
        case 1:
            return "逾期"
        default:
            return "已补签"
        }
    }
}

extension TimeInterval {
    func string(format: String = "hh.mm EEEE dd MMM") -> String {//"dd MMM hh.mm" 1月20日 上午11:10
        let formatter = DateFormatter()
        formatter.setLocalizedDateFormatFromTemplate(format)
        formatter.locale = Locale.current
        return formatter.string(from: Date(timeIntervalSince1970: self))
    }
}

func Patch2023Beta3Fix() {
    if UserDefaults(suiteName: "group.classmanager")!.string(forKey: "server") != nil {
        return
    }
    
    
    UserDefaults(suiteName: "group.classmanager")!.set(UserDefaults.standard.string(forKey: "server"), forKey: "server")
    UserDefaults(suiteName: "group.classmanager")!.set(UserDefaults.standard.string(forKey: "downloadFrom"), forKey: "downloadFrom")
    UserDefaults(suiteName: "group.classmanager")!.set(UserDefaults.standard.string(forKey: "postTo"), forKey: "postTo")
    UserDefaults(suiteName: "group.classmanager")!.set(UserDefaults.standard.string(forKey: "httpServer"), forKey: "httpServer")
    UserDefaults(suiteName: "group.classmanager")!.set(UserDefaults.standard.string(forKey: "httpServerPort"), forKey: "httpServerPort")
    UserDefaults(suiteName: "group.classmanager")!.set(UserDefaults.standard.string(forKey: "teacherName"), forKey: "teacherName")
}

// server
// http://192.168.31.201:8080/
// upload.php?fileName=$fileName$
// uploadFiles/$fileName$
